#include "server.h"
extern "C" {
#include <netinet/tcp.h>
#include <netinet/in.h>
}
#include <epoll.h>
#include <system_error>
#include <iostream>
#include "loop.h"
#include "connection.h"

using namespace std;

#define PRE_FD 8


Server::Server(event_loop* l, int max_connections)
:asocket(AF_INET, SOCK_STREAM, 0),
connections(max_connections), valid_conn_indices(max_connections) {
    //  set fd as 3
    if(sock.fileno() >= PRE_FD) {
        throw runtime_error("Server::Server() error: Server fd >= PRE_FD, please close extra fd");
    }

    event_mask = EPOLLERR|EPOLLIN|EPOLLPRI;
    sock.set_block(false);

    //
    for(int i=0; i<max_connections; ++i) {
        valid_conn_indices[i] = i + PRE_FD;
        int fd = socket(AF_INET, SOCK_STREAM, 0);
        if(dup2(fd, i+PRE_FD) == -1) {
            std::string err("Server::Server() dup2 error: ");
            err += strerror(errno);
            throw std::system_error(errno, std::system_category(), err);
        }
        connections[i].open(i+PRE_FD, l);
        connections[i].svr = this;
    }
}

Server::~Server() {

}

void Server::on_read() {
    class socket tmp;
    while(!valid_conn_indices.empty() && (tmp = sock.accept()).valid()) {
        int ret = valid_conn_indices.back();
        if (ret != tmp.fileno()) {
            if (dup2(tmp.fileno(), ret) == -1) {
                std::string err("tcp Server::on_read dup2() error: ");
                err += strerror(errno);
                throw std::system_error(errno, std::system_category(), err);
            }
            tmp.close();
        }

        //! assign connect handle
        auto &conn = connections[ret - PRE_FD];
        valid_conn_indices.pop_back();

        ///  http Server do something
        conn.connect();
    }
}

void Server::on_close() {
    cout << "on close" << endl;
    loop->rmv_socket(sock.fileno());
    sock.close();
    if(valid_conn_indices.size() < connections.size()) {
        loop->set_timeout(1000, [this]() {
            closing();
        });
    }else {
        loop = nullptr;
    }
}

void Server::listen(const std::string &host, uint16_t port, int backlog) {
    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1);
    sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
    sock.bind(std::forward<const std::string&>(host), port);
    sock.listen(backlog);
    std::cout << "tcp-Server listen on " << sock.local.host << ":" << sock.local.port << std::endl;
}

